package stegosaur.volume;

import java.io.File;

/**
 * <p>
 * The VolumeParameters class contains information about a Volume.  This is useful
 * in two scenarios:
 * <ul>
 * <li>When creating a Volume, first create an instance of this class to specify
 *     the volume parameters.
 * <li>When examining an existing Volume, Volume.getVolumeParameters() will return 
 *     an instance of this class that you can then examine.
 * </ul>
 * </p>
 *
 * @see stegosaur.Volume
 * @author gndn.k5
 * @since Stegosaur 1.0
 */
public final class VolumeParameters
{
  public static final byte SEQUENTIAL_ALWAYS = (byte)0;
  public static final byte SEQUENTIAL_USUALLY = (byte)1;
  public static final byte SEQUENTIAL_SOMETIMES = (byte)2;
  public static final byte SEQUENTIAL_RARELY = (byte)3;
  public static final byte SEQUENTIAL_NEVER = (byte)4;
  
  public static final byte REDUNDANT_VERY = (byte)3;
  public static final byte REDUNDANT_SORTOF = (byte)2;
  public static final byte REDUNDANT_NEVER = (byte)1;

  /** Default constructor does nothing. **/
  public VolumeParameters() {}
  
  /** Copy constructor **/
  public VolumeParameters(VolumeParameters other)
  {
    this.name = other.name;
    this.description = other.description;
    if (other.masterFile != null)
      this.masterFile = new File(other.masterFile.getAbsolutePath());
    this.splitFileNameFormat = other.splitFileNameFormat;
    this.blockSize = other.blockSize;
    this.capacityMB = other.capacityMB;
    this.preferSequential = other.preferSequential;
    this.preferRedundant = other.preferRedundant;
  }

  /** Human-readable name of this volume.  (No default value). **/
  public String name;
  
  /** Description of the volume.  (No default value).  **/
  public String description;

  /** Contains absolute path to the volume master file. (No default value). */
  public File masterFile;

  /** 
   * Specifies the size, in MB, after which the datastore will be
   * split into multiple files.  A value of zero means to never
   * split the datastore (i.e. all in one file).  A value of
   * 1024 would result in multiple 1GB datastore files.
   * The default value is 2048.
   */
  public int splitFileSizeMB = 2048;
  
  /**
   * <p>
   * A format string used to determine the datastore file
   * name(s).  The datastore file(s) are generated in the same
   * directory as the master file.
   * </p>
   * <p>
   * Use a %d token where you want the split file number to
   * appear in the filename.  You can optionally specify a minimum
   * width in the form %#d (the number will be left padded with
   * zeros if necessary to fill the minimum width).  If a file
   * extension is not specified, ".stdata" is used.
   * </p>
   * <p><b>Examples:</b>
   * <ul>
   * <li><b>MyDatastore%d.blah</b> will result in files named 
   *   MyDataStore1.blah, MyDataStore2.blah, and so on.
   * <li><b>MyDataStore%3d.blah</b> will result in files named
   *   MyDataStore001.blah, MyDataStore002.blah, and so on.
   * <li><b>store%4d</b> will result in files named store0001.stegsplit,
   *   store0002.stdata, and so on (note the implied extension).
   * </ul>
   * </p>
   * <p>Omitting the %d will result in a VolumeParametersException
   *    when attempting to create the volume.</p>
   * <p>The default value is "stegosaur%2d".</p>
   */
  public String splitFileNameFormat = "stegosaur%2d";
  
  /** The size of each block (defaults to 4KB). **/
  public int blockSize = 4096;

  /** The desired datastore capacity, in MB (defaults to 1024M). **/
  public int capacityMB = 1024;
  
  /** 
   * Describes the tendency to write items in sequential, contiguous blocks.
   * Setting to SEQUENTIAL_NEVER guarantees that items will be written randomly
   * to the datastore, thereby decreasing performance but increasing security.
   * Note that SEQUENTIAL_ALWAYS does not guarantee that items will be written
   * to a contiguous group of blocks... it may not be possible to do so 
   * depending on volume fragmentation and the size of the item to insert.
   * Defaults to SEQUENTIAL_NEVER.
   */
  public byte preferSequential = SEQUENTIAL_NEVER;
  
  /**
   * Describes the tendency to write multiple copies of each inserted item (in 
   * order to decrease the probability of data corruption when inserting items
   * without all layers open).  Setting to REDUNDANT_VERY will decrease performance
   * (and available volume space) but will increase reliability of item 
   * retrievals by giving the system a way to recover partially overwritten
   * data.  Set this to REDUNDANT_NEVER to only write a single copy of
   * each item.  Defaults to REDUNDANT_SORTOF.
   */
  public byte preferRedundant = REDUNDANT_SORTOF;
  
  /**
   * This method validates the splitFileNameFormat property and returns
   * the zero-pad number, if there is one (zero if there isn't).
   * A VolumeParametersException is thrown if the name format is invalid.
   * This method is invoked automatically by Volume.createVolume().
   */
  public int validateSplitFileNameFormat() throws VolumeParametersException
  {
    int zeroPadCount = 0;
    boolean validFormat = true;
    int tokenIndex = splitFileNameFormat.indexOf("%");

    // Do some basic checks on it:    
    if (tokenIndex == -1 ||                                   // If there is no % character
        tokenIndex != splitFileNameFormat.lastIndexOf("%") || // Or if there's more than one %
        tokenIndex > (splitFileNameFormat.length() - 2))      // Or if the % is too close to the end of the string
      validFormat = false;
     
    // No obvious problems so far:
    else
    {
      // Must match at least one of these patterns:
      if (! splitFileNameFormat.matches(".*%\\dd.*") && // %#d where # is any digit
          ! splitFileNameFormat.matches(".*%d.*"))      // %d
        validFormat = false;
        
      // Extract the actual number, if one is specified:
      if (validFormat && splitFileNameFormat.matches(".*%\\dd.*"))
      {
        String candidate = splitFileNameFormat.substring(tokenIndex+1, tokenIndex+2);
        try
        {
          zeroPadCount = Integer.parseInt(candidate);
        }
        catch (NumberFormatException nfe)
        {
          validFormat = false;
        }
      }
    }
    
    if (! validFormat)
      throw new VolumeParametersException("Syntax error on split file name format.");

    return zeroPadCount;    
  }
  
  /**
   * Formats and returns a split file name with the given embedded number.
   * See splitFileNameFormat for more details.  Will throw an exception
   * if the given number is less than or equal to zero.
   */
  public String getSplitFileName(int number) throws VolumeParametersException
  {
    if (number <= 0)
      throw new VolumeParametersException("Specified data file number cannot be less than or equal to zero.");
      
    validateSplitFileNameFormat();
    String stupidFix = splitFileNameFormat.replace("%","%0");
    String candidate = String.format(stupidFix, number);
    if (candidate.indexOf(".") == -1)
      candidate += ".stdata";
      
    return candidate;
  }
  
  /**
   * Computes and returns the absolute path of the index file, based on the path
   * and name of the master file.  The index file will be generated with the same
   * name as the master file but an extension of .idx (and will reside in the 
   * same directory as the master file).  Will return null if master file is null.
   */
  public File getIndexFile()
  {
    if (masterFile == null)
      return null;
      
    String path = masterFile.getPath();
    String name = masterFile.getName();
    int lastDot = name.lastIndexOf(".");
    if (lastDot != -1)
      name = name.substring(0,lastDot);
    name = name + ".idx";
    
    return new File(path, name);
  }
}
